import Ref from "../ref/Ref.js";

export default class Rente {

  elements = null;

  /** @return { RenderConfig } */
  render() {
    return { tag: "div" };
  }

  /**
   * @param { RenderConfigType } config
   * @return { Node }
   */
  createRenderConfigType(config) {
    if (
      ["string", "number", "boolean"].includes(typeof config)
      || config instanceof Date
    ) {
      return new Text(config.toString());
    } else if(typeof config === "object") {
      const { tag, attrs, children } = config;
      if (tag === "style") {
        const styleElement = document.createElement("style");
        styleElement.innerHTML = attrs.map(([className, classAttrs], insertIndex) => {
          return `${className} {${
            classAttrs.map(([key, value]) => {
              if (value instanceof Ref) {
                value.bind((val) => {
                  styleElement.sheet.cssRules[insertIndex].style[key] = val;
                });
                return `${key}: ${value.value};`
              } else {
                return `${key}: ${value};`
              }
            }).join("")
          }}`;
        }).join("\n");
        return styleElement;
      } else {
        const element = document.createElement(tag);
        if (attrs) {
          for (const name in attrs) {
            const value = attrs[name];
            if (name === "style" && typeof value === "object") {
              for (const styleName in value) {
                const styleValue = value[styleName];
                if (styleValue instanceof Ref) {
                  element[name][styleName] = styleValue.value;
                  styleValue.bind((styleValue) => {
                    element[name][styleName] = styleValue;
                  });
                } else {
                  element[name][styleName] = styleValue;
                }
              }
            } else if (value instanceof Ref) {
              element[name] = value.value;
              value.bind((attrValue) => {
                element[name] = attrValue;
              });
            } else {
              element[name] = value;
            }
          }
        }
        if (children) {
          for (const child of children) {
            this.createRender(element, child);
          }
        }
        return element;
      }
    }
  }

  /**
   * @param { RenderConfig } config
   */
  createRenderConfig(config) {
    if(config instanceof Rente) {
      return this.createRenderConfig(config.render());
    } else if(config instanceof Ref) {
      return Ref.calc((config) =>  this.createRenderConfig(config), config);
    } else if (Array.isArray(config)) {
      return config.map(item => this.createRenderConfig(item));
    } else {
      return this.createRenderConfigType(config);
    }
  }

  /**
   * @param { Node } el
   * @param { RenderConfig } config
   */
  createRender(el, config) {
    this.createElement(el, this.createRenderConfig(config));
  }

  /**
   * @param { Node } el
   * @param { Node | Node[] | Ref<Node | Node[]> } config
   * @param { Text } [point]
   */
  createElement(el, config, point) {
    if (config instanceof Ref) {
      const point = new Text();

      this.createElement(el, config.value);
      el.appendChild(point);

      config.bind((newValue, oldValue) => {
        if (Array.isArray(oldValue)) {
          for (const element of oldValue) {
            el.removeChild(element);
          }
        } else {
          el.removeChild(oldValue);
        }
        this.createElement(el, newValue, point);
      });
    } else if(Array.isArray(config)) {
      for (const element of config) {
        this.createElement(el, element, point);
      }
    } else {
      if (point) {
        el.insertBefore(config, point);
      } else {
        el.appendChild(config);
      }
    }
  }

  /** @param { string } target */
  mount(target) {
    const el = document.querySelector(target);
    this.elements = this.createRender(el, this.render());
  }
}
